package cn.wolfcode.shop.cloud.service.impl;

import cn.wolfcode.shop.cloud.common.AssertUtil;
import cn.wolfcode.shop.cloud.common.BusinessException;
import cn.wolfcode.shop.cloud.common.IdGenerateUtil;
import cn.wolfcode.shop.cloud.common.JSONUtil;
import cn.wolfcode.shop.cloud.domain.OrderInfo;
import cn.wolfcode.shop.cloud.domain.User;
import cn.wolfcode.shop.cloud.mapper.OrderInfoMapper;
import cn.wolfcode.shop.cloud.mq.DefaultSeckillOrderMsgSendCallback;
import cn.wolfcode.shop.cloud.mq.MQConstants;
import cn.wolfcode.shop.cloud.mq.msg.CreateSeckillOrderMsg;
import cn.wolfcode.shop.cloud.redis.key.SeckillRedisKey;
import cn.wolfcode.shop.cloud.redis.key.UserRedisKey;
import cn.wolfcode.shop.cloud.service.IOrderInfoService;
import cn.wolfcode.shop.cloud.service.ISeckillGoodService;
import cn.wolfcode.shop.cloud.service.ISeckillOrderService;
import cn.wolfcode.shop.cloud.vo.SeckillGoodVo;
import cn.wolfcode.shop.cloud.web.controller.OrderInfoController;
import cn.wolfcode.shop.cloud.web.msg.SeckillServerCodeMsg;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * Created by wolfcode-lanxw
 */
@Slf4j
@Service
public class OrderInfoServiceImpl implements IOrderInfoService {

    @Autowired
    private ISeckillOrderService seckillOrderService;
    @Autowired
    private ISeckillGoodService seckillGoodService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doSeckill(Long seckillId, String uuid, String token, String random) {
        User user = this.findUserByToken(token);
        AssertUtil.notNull(user, SeckillServerCodeMsg.NOT_AUTH_ERROR);

        // 校验url是否合法
        String randomInRedis = redisTemplate.opsForValue().get(SeckillRedisKey.SECKILL_RANDOM_PATH.join(seckillId + "", user.getId() + ""));
        if (!random.equals(randomInRedis)) {
            throw new BusinessException(SeckillServerCodeMsg.OP_ERROR);
        }

        // 根据秒杀id查询秒杀信息
        SeckillGoodVo vo = seckillGoodService.findByCache(seckillId);
        AssertUtil.notNull(vo, SeckillServerCodeMsg.DEFALUT_ERROR);

        // 执行秒杀前的前置校验逻辑
        this.validate(vo, user.getId());

        // 修改为异步下单, 发送 MQ 消息
        rocketMQTemplate.asyncSend(MQConstants.CREATE_ORDER_DEST, new CreateSeckillOrderMsg(seckillId, user.getId(), uuid), new DefaultSeckillOrderMsgSendCallback());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String seckillOrder(Long seckillId, Long userId) {
        // 根据秒杀id查询秒杀信息
        SeckillGoodVo vo = seckillGoodService.findByCache(seckillId);
        AssertUtil.notNull(vo, SeckillServerCodeMsg.DEFALUT_ERROR);

        // 1. 扣减库存
        int rows = seckillGoodService.decrStockCount(seckillId);
        // 如果 rows <= 0 就拋出库存不足的异常
        AssertUtil.isTrue(rows > 0, SeckillServerCodeMsg.OUT_OF_STOCK_ERROR);
        // 2. 创建订单对象
        String orderNo = this.createOrder(vo, userId);
        // 3. 创建秒杀订单
        seckillOrderService.createSeckillOrder(userId, seckillId, orderNo);

        return orderNo;
    }

    @Override
    public OrderInfo findById(String orderNo, String token) {
        User user = findUserByToken(token);
        AssertUtil.notNull(user, SeckillServerCodeMsg.DEFALUT_ERROR);

        return orderInfoMapper.selectByPrimaryKey(orderNo, user.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkTimeout(String orderNo, Long seckillId) {
        // 1. 检查订单状态, 如果未支付, 修改订单状态
        int rows = orderInfoMapper.updateStatus(orderNo);
        if (rows > 0) {
            // 2. 如果订单未支付, 回补 MySQL 库存
            seckillGoodService.incrStockCount(seckillId);
            SeckillGoodVo vo = seckillGoodService.findById(seckillId);
            // 3. 同步 MySQL 库存到 Redis 中
            redisTemplate.opsForHash().put(
                    SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.join(),
                    seckillId + "",
                    vo.getStockCount() + ""
            );
            // 4. 发送广播消息, 清除本地售完标记
            rocketMQTemplate.asyncSend(MQConstants.CLEAR_STOCK_COUNT_FLAG_ORDER_DEST, seckillId, new DefaultSeckillOrderMsgSendCallback());
        }
    }

    @Override
    public void handleFailed(Long seckillId, Long userId) {
        // 1. 同步 MySQL 库存到 Redis 中
        SeckillGoodVo vo = seckillGoodService.findById(seckillId);
        redisTemplate.opsForHash().put(
                SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.join(),
                seckillId + "",
                vo.getStockCount() + ""
        );
        // 2. 清除本地售完标记
        rocketMQTemplate.asyncSend(MQConstants.CLEAR_STOCK_COUNT_FLAG_ORDER_DEST, seckillId, new DefaultSeckillOrderMsgSendCallback());
        // 3. 清除用户下单标记
        redisTemplate.delete(SeckillRedisKey.USER_SECKILL_RECORD.join(vo.getId() + "", userId + ""));
    }

    @Override
    public void paySuccess(String orderNo) {
        orderInfoMapper.updatePayStatus(orderNo);
    }

    private String createOrder(SeckillGoodVo vo, Long userId) {
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(vo, orderInfo);
        orderInfo.setUserId(userId);
        orderInfo.setCreateDate(new Date());
        orderInfo.setGoodCount(1);
        String orderNo = IdGenerateUtil.get().nextId() + "";
        orderInfo.setOrderNo(orderNo);
        orderInfoMapper.insert(orderInfo);
        return orderNo;
    }

    private void validate(SeckillGoodVo vo, Long userId) {
        // 判断时间
        Date now = new Date();
        // 活动尚未开始: 如果当前时间小于结束时间成立, 就报错
        AssertUtil.isFalse(now.compareTo(vo.getStartDate()) < 0, SeckillServerCodeMsg.NOT_START_ERROR);
        // 活动已经结束: 如果当前时间 > 结束时间, 就报错
        AssertUtil.isFalse(now.compareTo(vo.getEndDate()) > 0, SeckillServerCodeMsg.SECKILL_OVER_ERROR);
        // 判断用户是否重复下单
        //  SeckillOrder seckillOrder = seckillOrderService.findByUserIdAndSeckillId(userId, vo.getId());
        //  AssertUtil.isNull(seckillOrder, SeckillServerCodeMsg.REPEATER_ORDER_ERROR);
        Boolean isSeckcill = redisTemplate.opsForValue().setIfAbsent(
                SeckillRedisKey.USER_SECKILL_RECORD.join(vo.getId() + "", userId + ""), "1");
        AssertUtil.isTrue(isSeckcill == null || isSeckcill, SeckillServerCodeMsg.REPEATER_ORDER_ERROR);
        // 判断库存是否足够
        Long ret = redisTemplate.opsForHash().increment(
                SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.join(),
                vo.getId() + "",
                -1);
        // 只有库存 >= 0 的情况才允许进行秒杀, < 0 直接报错
        if (ret < 0) {
            // 增加售完标记
            log.info("用户:{}秒杀失败, 标记本地售完标记:{}", userId, vo.getId());
            // 清除用户已经下单标识
            redisTemplate.delete(SeckillRedisKey.USER_SECKILL_RECORD.join(vo.getId() + "", userId + ""));
            OrderInfoController.STOCK_OVER_FLAG_MAP.put(vo.getId(), true);
            throw new BusinessException(SeckillServerCodeMsg.OUT_OF_STOCK_ERROR);
        }
        // AssertUtil.isFalse(vo.getStockCount() <= 0, SeckillServerCodeMsg.OUT_OF_STOCK_ERROR);
    }

    private User findUserByToken(String token) {
        String json = redisTemplate.opsForValue().get(UserRedisKey.USER_TOKEN.join(token));
        return JSONUtil.parseObject(json, User.class);
    }
}
